proto3 语法
1、定义消息类型
这是.proto
用于定义消息类型的文件。
syntax = "proto3"
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
• 该文件的第一行指定您正在使用proto3
语法:如果您不这样做,协议缓冲区编译器将假定您正在使用proto2
。这必须是文件的第一个非空的非注释行。
• 所述SearchRequest
消息定义指定了三个字段(名称/值对),一个用于要在此类型的消息中包含的每个数据片段。每个字段都有一个名称和类型。
1.1 指定字段类型
在上面的示例中,所有字段都是标量类型 :两个整数(page_number
和 result_per_page
)和一个字符串(query
)。但是,您还可以为字段指定复合类型,包括枚举和其他消息类型。
1.2 分配字段编号
如您所见,消息定义中的每个字段都有唯一的编号。这些字段编号用于以消息二进制格式标识字段,并且在使用消息类型后不应更改。
请注意,1
到15
范围内的字段编号需要一个字节进行编码,包括字段编号和字段类型。16
到2047
范围内的字段编号占用两个字节。因此,您应该为非常频繁出现的消息元素保留数字1
到15
。请记住为将来可能添加的常用元素留出一些空间。
您可以指定的最小字段数为1
,最大字段数为536,870,911
。您也不能使用数字19000
到19999
,因为它们是为Protobuf
实现保留的,如果您使用其中一个保留号码,编译器会报错。同样,您不能使用任何以前保留的字段编号。
1.3 指定字段规则
消息字段可以是以下之一:
• 单个字段:字段不能重复。
• repeated:此字段可以在格式良好的消息中重复任意次数(包括零)。将保留重复值的顺序。
在proto3
中,repeated
标量数字类型的字段默认使用packed
编码。
您可以在协议缓冲区编码中找到有关packed
编码的更多信息。
1.4 添加更多消息类型
可以在单个.proto
文件中定义多种消息类型。如果要定义多个相关消息,这很有用。例如,如果要定义与SearchResponse
消息类型对应的回复消息格式,可以将其添加到.proto
:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
1.5 添加注释
要为.proto
文件添加注释,请使用//
和/* ... */
语法。
/* SearchRequest 表示搜索查询,其中的分页选项
* 表示要包含在响应中的结果。 */
message SearchRequest {
string query = 1;
int32 page_number = 2; // 我们想要哪个页码?
int32 result_per_page = 3; // 每页返回的结果数。
}
1.6 保留字段
如果通过完全删除字段或将其注释来更新消息类型,则未来用户可以在对类型进行自己的更新时重用字段编号。如果以后加载相同的.proto
旧版本,这可能会导致严重问题,包括数据损坏,隐私错误等。
确保不会发生这种情况的一种方法是通过reserved
指定已删除字段的字段编号(或名称,这也可能导致JSON序列化问题)。如果将来的任何用户尝试使用这些字段标识符,编译器将会报错。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
请注意,您不能在同一
reserved
语句中混合字段名称和字段编号。
1.7 你的.proto生成什么?
当您通过编译器protoc.exe
编译.proto
以后,编译器会生成您所选语言的代码,您需要使用您在文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,并从输入流解析您的消息。
• 对于C#,编译器会从每个.proto
文件生成一个.cs
文件,其中包含文件中描述的每种消息类型的类。
编译指令:
protoc.exe --proto_path=./ xxx.proto --csharp_out=./
2、标量值类型
标量消息字段可以具有以下类型之一,该表显示.proto
文件中指定的类型,以及自动生成的类中的相应类型:
.proto类型 | C#类型 | 说明 |
---|---|---|
double | double | |
float | float | |
int32 | int | 使用可变长度编码。编码负数的效率低,如果您的字段可能有负值,请改用sint32 。 |
int64 | long | 使用可变长度编码。编码负数的效率低,如果您的字段可能有负值,请改用sint64 。 |
uint32 | uint | 使用可变长度编码。 |
uint64 | ulong | 使用可变长度编码。 |
sint32 | int | 使用可变长度编码。签名的int 值。这些比常规int32 更有效地编码负数。 |
sint64 | long | 使用可变长度编码。签名的int 值。这些比常规int64 更有效地编码负数。 |
fixed32 | uint | 总是四个字节。如果值通常大于2^28 ,则比uint32 更有效。 |
fixed64 | ulong | 总是八个字节。如果值通常大于2^56 ,则比uint64 更有效。 |
sfixed32 | int | 总是四个字节。 |
sfixed64 | long | 总是八个字节。 |
bool | bool | |
string | string | 字符串必须始终包含UTF-8编码或7位ASCII文本。 |
bytes | ByteString | 可以包含任意字节序列。 |
3、默认值
解析消息时,如果编码消息不包含特定的单数元素,则解析对象中的相应字段将设置为该字段的默认值。
这些默认值是特定于类型的:
对于字符串,默认值为空字符串。
对于字节,默认值为空字节。
对于`bool`,默认值为`false`。
对于数字类型,默认值为零。
对于枚举,默认值是第一个定义的枚举值,该值必须为`0`。
对于消息字段,未设置该字段。它的确切值取决于语言。
有关详细信息,请参阅生成的代码。重复字段的默认值为空(通常是相应语言的空列表)。
4、枚举
在定义消息类型时,您可能希望其中一个字段只有一个预定义的值列表。例如,假设你想每个SearchRequest
添加一个corpus
字段,其中取值可以UNIVERSAL
,WEB
,IMAGES
,LOCAL
,NEWS
,PRODUCTS
或VIDEO
。您可以非常简单地通过enum
为每个可能的值添加一个常量来定义消息定义。
在下面的示例中,我们添加了一个带有所有可能值的enum
调用Corpus
,以及一个类型的字段Corpus
:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
如您所见,Corpus
枚举的第一个常量映射为零:每个枚举定义必须包含一个映射到零的常量作为其第一个元素。这是因为:
• 必须有一个零值,以便我们可以使用 0 作为数字默认值 。
• 零值必须是第一个元素,以便与proto2 语义兼容,其中第一个枚举值始终是默认值。
您可以通过为不同的枚举常量指定相同的值来定义别名。为此,您需要将allow_alias
选项设置为true
,否则协议编译器将在找到别名时生成错误消息。
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // 取消注释此行将导致 Google 内部的编译错误和外部的警告消息。
}
枚举器常量必须在32位整数范围内。由于enum
值在online
使用varint
编码,因此负值效率低,因此不建议使用。
enum
也可以在外部定义,这些可以在.proto
文件的任何消息定义中重用。您还可以使用enum
语法将一个消息中声明的类型用作另一个消息中的字段类型:MessageType.EnumType
。
在反序列化期间,将在消息中保留无法识别的枚举值,但是当反序列化消息时,如何表示这种值取决于语言。在支持具有超出指定符号范围的值的开放枚举类型的语言中,例如C++和Go,未知的枚举值仅作为其基础整数表示存储。在具有封闭枚举类型(如Java)的语言中,枚举中的大小写用于表示无法识别的值,并且可以使用特殊访问器访问基础整数。在任何一种情况下,如果消息被序列化,则仍然会使用消息序列化无法识别的值。
4.1 保留值
如果通过完全删除枚举条目或将其注释掉来更新枚举类型,则未来用户可以在对类型进行自己的更新时重用该数值。如果以后加载相同的.proto
旧版本,这可能会导致严重问题,包括数据损坏,隐私错误等。确保不会发生这种情况的一种方法是通过reserved
指定已删除条目的数值(或名称,这也可能导致JSON序列化问题)。如果将来的任何用户尝试使用这些标识符,编译器将会报错。您可以使用max
关键字指定保留的数值范围达到最大可能值。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
请注意,您不能在同一reserved
语句中混合字段名称和数值。
5、使用其他消息类型
您可以使用其他消息类型作为字段类型。例如,假设你想SearchResponse
消息包括Result
的每个消息,要做到这一点,你可以在.proto
定义一个Result
消息类型,然后指定SearchResponse
类型中的字段Result
:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
5.1 导入定义
在上面的示例中,Result
消息类型和SearchResponse
在同一文件中定义,如果要用作字段类型的消息类型已在另一个.proto
文件中定义,该怎么办?
您可以通过导入来使用其他.proto
文件中的定义。要导入其他.proto
的定义,请在文件顶部添加import
语句:
import "myproject/other_protos.proto";
默认情况下,您只能使用直接导入.proto
文件中的定义。但是,有时您可能需要将.proto
文件移动到新位置。您可以在旧位置放置一个虚拟.proto
文件,以使用该import public
概念将所有导入转发到新位置,而不是直接移动文件并在一次更改中更新所有调用站点。任何导入包含该import public
语句的proto
都可以传递依赖关系。例如:
// new.proto
// 所有定义都移到这里
// old.proto
// 这是所有客户端都要导入的原型。
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// 您使用old.proto和new.proto中的定义,但不使用other.proto
5.2 使用proto2消息类型
可以导入proto2
消息类型并在proto3
消息中使用它们,反之亦然。但是,proto2
枚举不能直接用于proto3
语法(如果导入的proto2
消息使用它们就可以了)。
6、嵌套类型
您可以在其他消息类型中定义和使用消息类型,如下例所示:此处 Result
消息在SearchResponse
消息中定义:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果要在其父消息类型之外重用此消息类型,请将其称为:Parent.Type
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
您可以根据需要深入嵌套消息:
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
7、更新消息类型
如果现有的消息类型不再满足您的所有需求 - 例如,您希望消息格式具有额外的字段 - 但您仍然希望使用使用旧格式创建的代码,请不要担心!在不破坏任何现有代码的情况下更新消息类型非常简单。请记住以下规则:
• 请勿更改任何现有字段的字段编号。
• 如果添加新字段,则使用 “旧” 消息格式按代码序列化的任何消息仍可由新生成的代码进行解析。您应该记住这些元素的默认值,以便新代码可以正确地与旧代码生成的消息进行交互。同样,您的新代码创建的消息可以由旧代码解析:旧的二进制文件在解析时只是忽略新字段。有关详细信息,请参阅 “ 未知字段 ” 部分
• 只要在更新的消息类型中不再使用字段编号,就可以删除字段。您可能希望重命名该字段,可能添加前缀 “OBSOLETE_” ,或者保留字段编号,以便您的未来用户不会意外地重复使用该编号。
• int32
,uint32
,int64
,uint64
,和bool
都是兼容的 - 这意味着你可以改变这些类型向前或向后兼容。如果从文件中解析出一个不符合相应类型的数字,您将获得与在 C ++ 中将该数字转换为该类型相同的效果(例如,如果将 64 位数字作为 int32 读取,它将被截断为 32 位)。
• sint32
和sint64
彼此兼容但与其他整数类型不兼容。
• string
和bytes
,只要字节是有效的UTF-8,它们是兼容的。
• bytes
如果字节包含消息的编码版本,则嵌入消息是兼容的。
• fixed32
兼容sfixed32
,并且fixed64
兼容sfixed64
。
• enum
兼容int32
,uint32
,int64
,和 uint64
(注意,如果他们不适合的值将被截断)。但请注意,在反序列化消息时,客户端代码可能会以不同方式对待它们:例如,enum
将在消息中保留未识别的proto3
类型,但在反序列化消息时如何表示这种类型取决于语言。int
字段总是保留它们的值。
• 将单个值更改为新成员oneof
是安全且二进制兼容的。如果您确定没有代码一次设置多个字段,则将多个字段移动到新字段可能是安全的。将任何字段移动到现有字段oneof
并不安全。
8、未知字段
未知字段是格式良好的协议缓冲区序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件解析具有新字段的新二进制文件发送的数据时,这些新字段将成为旧二进制文件中的未知字段。
最初,proto3
消息在解析期间总是丢弃未知字段,但在 3.5 版本中,我们重新引入了保存未知字段以匹配proto2
行为。在版本 3.5 及更高版本中,未知字段在解析期间保留并包含在序列化输出中。
9、Any消息类型
该Any
消息类型,可以使用网址作为嵌入式类型,而不必自己.proto
定义。一个Any
含有任意的序列化消息bytes
,以充当一个全局唯一标识符和解析到该消息的类型的URL一起。要使用该Any
类型,您需要导入google/protobuf/any.proto
。
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
给定消息类型的默认类型URL是type.googleapis.com/packagename.messagename
10、Oneof
如果您有一个包含许多字段的消息,并且最多只能同时设置一个字段,则可以使用oneof
功能强制执行此行为并节省内存。
除了一个共享内存中的所有字段之外,其中一个字段类似于常规字段,并且最多可以同时设置一个字段。设置oneof
的任何成员会自动清除所有其他成员。您可以使用特殊case()
或WhichOneof()
方法检查oneof
中的哪个值(如果有),具体取决于您选择的语言。
10.1 使用Oneof
要在您 .proto
中定义 oneof
,请使用 oneof
关键字后跟您的 oneof
名称,在这种情况下 test_oneof
:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后,将oneof
字段添加到oneof
定义中。您可以添加任何类型的字段,但不能使用repeated
字段。
10.2 Oneof特点
• 设置oneof
字段将自动清除oneof
的所有其他成员。因此,如果您设置了多个字段,则只有您设置的最后一个字段仍然具有值。
• 如果解析器遇到同一个oneof
的多个成员,则在解析的消息中仅使用看到的最后一个成员。
• 字段不能使用repeated
。
• Reflection API
适用于其中一个字段。
10.3 向后兼容问题
添加或删除其中一个字段时要小心。如果检查oneof
返回的值None/NOT_SET
,这可能意味着oneof
尚未设置或已在不同版本的oneof
的被设置为一个字段。没有办法区分,因为没有办法知道线上的未知字段是否是其中一个成员。
标签重用问题
• 将字段移入或移出oneof
:在序列化和解析消息后,您可能会丢失一些信息(某些字段将被清除)。但是,您可以安全地将单个字段移动到新的 oneof
中,并且如果已知只有一个字段被设置,则可以移动多个字段。
• 删除oneof
字段并将其添加回:在序列化和解析消息后,这可能会清除当前设置的oneof
字段。
• 拆分或合并oneof
:这与移动常规字段有类似的问题。
11、映射
如果要在数据定义中创建关联映射,protobuf
提供了一种方便的快捷方式语法:
map<key_type,value_type> map_field = N;
... 其中key_type
可以是任何整数或字符串类型(因此,除了浮点类型之外的任何标量、bytes
类型)。请注意,枚举不是有效的 key_type
。value_type
可以是任何类型的除另一映射。
因此,例如,如果要创建项目映射,其中每条Project
消息都与字符串键相关联,则可以像下面这样定义它:
map<string,Project> projects = 3;
• 映射字段不能repeated
。
• 映射值的有线格式排序和映射迭代排序未定义,因此您不能依赖于特定顺序的映射项目。
• 为.proto
生成文本格式时,映射按键排序。数字键按数字排序。
• 解析或合并时,如果有重复的映射键,则使用最后看到的键。从文本格式解析映射时,如果存在重复键,则解析可能会失败。
• 如果为映射字段提供键但没有值,则字段序列化时的行为取决于语言。在C++
,Java
和Python
中,类型的默认值是序列化的,而在其他语言中没有任何序列化。
11.1 向后兼容性
映射语法在线上等效于以下内容,因此不支持映射的Protobuf
实现仍可处理您的数据:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
任何支持映射的Protobuf
实现都必须生成和接受上述定义可以接受的数据
12、包
您的package
可以向.proto
文件添加可选说明符,以防止协议消息类型之间的名称冲突。
package foo.bar;
message Open { ... }
然后,您可以在定义消息类型的字段时使用包说明符:
message Foo {
...
foo.bar.Open open = 1;
...
}
包说明符影响生成的代码的方式取决于您选择的语言:
• 在C#中,包转换为PascalCase
后用作命名空间,除非您在.proto
文件中明确提供option csharp_namespace
。例如,Open
将在命名空间Foo.Bar
中。
12.1 包和名称解析
Protobuf
语言中的类型名称解析与C++类似:首先搜索最里面的范围,然后搜索下一个范围,依此类推,每个包被认为是其父包的“内部”。一个领先的'.'(例如,.foo.bar.Baz
)意味着从最外层的范围开始。
Protobuf
编译器通过解析导入的.proto
文件来解析所有类型名称。每种语言的代码生成器都知道如何使用该语言引用每种类型,即使它具有不同的范围规则。
13、定义服务
如果要将消息类型与RPC(远程过程调用)系统一起使用,则可以在 .proto
文件中定义RPC服务接口,Protobuf
编译器将使用您选择的语言生成服务接口代码和存根。因此,例如,如果要使用带有SearchRequest
和返回的SearchResponse
方法定义RPC服务,可以按如下方式在.proto
文件中定义它:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
与Protobuf
一起使用的最简单的RPC系统是gRPC:一种在Google开发的语言和平台中立的开源RPC系统。gRPC特别适用于Protobuf
,并允许您使用特殊的Protobuf
编译器插件直接从您的.proto
文件生成相关的RPC代码。
14、JSON映射
Proto3支持JSON中的规范编码,使得在系统之间共享数据变得更加容易。在下表中逐个类型地描述编码。
如果JSON编码数据中缺少值,或者其值为null
,则在解析为Protobuf
时,它将被解释为适当的默认值。如果字段在Protobuf
中具有默认值,则默认情况下将在JSON编码数据中省略该字段以节省空间。实现可以提供用于在JSON编码的输出中发出具有默认值的字段的选项。
proto3 | JSON | JSON example | Notes |
---|---|---|---|
message |
object |
{"fooBar":v, "g":null,...} |
生成JSON对象。消息字段名称映射到lowerCamelCase 并成为JSON对象键。如果json_name 指定了field 选项,则指定的值将用作键。解析器接受lowerCamelCase 名称(或json_name 选项指定的名称)和原始proto 字段名称。null 是所有字段类型的可接受值,并将其视为相应字段类型的默认值。 |
enum |
string |
"FOO_BAR" |
使用proto 中指定的枚举值的名称。解析器接受枚举名称和整数值。 |
map<K,V> |
object |
{"k":v, ...} |
所有键都转换为字符串。 |
repeated V |
array |
[v, ...] |
null 被接受为空列表[]。 |
bool |
true, false |
true,false |
|
string |
string |
"hello world" |
|
bytes |
base64 string |
"YWJjMTIzIT8+" |
JSON值将是使用带填充的标准base64 编码编码为字符串的数据。接受带有/ 不带填充的标准或URL安全base64 编码。 |
int32,fixed32,uint32 |
number |
1,-10,0 |
JSON值将是十进制数。接受数字或字符串。 |
int64,fixed64,uint64 |
string |
"1", "-10" |
JSON值将是十进制字符串。接受数字或字符串。 |
float, double |
number |
1.1, -10.0, 0, "Nan", "Infinity" |
JSON值将是一个数字或一个特殊字符串值“NaN ”,“Infinity ”和“-Infinity ”。接受数字或字符串。指数表示法也被接受。 |
Any |
object |
{"@type":"url" "f": v, ...} |
如果Any 包含具有特殊JSON 映射的值,则将按如下方式进行转换:。否则,该值将转换为JSON对象,并将插入该字段以指示实际的数据类型。{"@type": xxx, "value": yyy}"@type" |
Timestamp |
string |
"1972-01-01T10:00:20.021Z" |
使用RFC3339 ,其中生成的输出将始终被Z标准化并使用0,3,6或9个小数位。也接受“Z ”以外的偏移。 |
Duration |
string |
"1.000340012s", "1s" |
生成的输出始终包含0 ,3 ,6 或9 个小数位,具体取决于所需的精度,后跟后缀“s ”。接受的是任何小数位(也没有),只要它们符合纳秒精度并且后缀“s ”是必需的。 |
Struct |
object |
{ ... } |
任何JSON对象。见struct.proto |
Wrapper types |
various types |
2,"2","foo",true,"true",null,0,... |
包装器在JSON中使用与包装基元类型相同的表示形式,除了null 在数据转换和传输期间允许和保留的表示形式。 |
FieldMask |
string |
"f.fooBar, h" |
见field_mask.proto |
ListValue |
value |
[foo, bar, ...] |
|
Value |
value |
任何JSON值 | |
NullValue |
null |
JSON null |
14.1 JSON选项
proto3 JSON 实现可以提供以下选项:
• 使用默认值发出字段:默认情况下,proto3
JSON输出中省略了具有默认值的字段。实现可以提供覆盖此行为的选项,并使用其默认值输出字段。
• 忽略未知字段:默认情况下,proto3
JSON解析器应拒绝未知字段,但可以提供忽略解析中未知字段的选项。
• 使用proto
字段名称而不是lowerCamelCase
名称:默认情况下,proto3
JSON打印机应将字段名称转换为lowerCamelCase
并将其用作JSON名称。实现可以提供使用proto
字段名称作为JSON名称的选项。proto3
JSON解析器需要接受转换后的lowerCamelCase
名称和proto
字段名称。
• 将枚举值发送为整数而不是字符串:默认情况下,在JSON输出中使用枚举值的名称。可以提供选项以使用枚举值的数值。
15、选项
.proto
文件中的各个声明可以使用许多选项进行注释。选项不会更改声明的整体含义,但可能会影响在特定上下文中处理它的方式。可用选项的完整列表在google/protobuf/descriptor.proto
中定义。
一些选项是文件级选项,这意味着它们应该在顶级范围内编写,而不是在任何消息,枚举或服务定义中。一些选项是消息级选项,这意味着它们应该写在消息定义中。一些选项是字段级选项,这意味着它们应该写在字段定义中。选项也可以写在枚举类型,枚举值,服务类型和服务方法上;但是,目前没有任何有用的选择。
15.1 自定义选项
Protocol Buffers
还允许您定义和使用自己的选项。这是大多数人不需要的高级功能。如果您确实认为需要创建自己的选项,请参阅Proto2
语言指南 以获取详细信息。请注意,创建自定义选项使用的扩展名仅允许用于proto3
中的自定义选项。
16、生成您的类
要生成你需要在定义的消息类型的工作使用 Java
, Python
, C++
, Go
, Ruby
, Objective-C
, or C#
代码 .proto
文件,你需要运行Protobuf
编译器protoc
编译.proto
。如果尚未安装编译器,请下载该软件包 并按照自述文件中的说明进行操作。
protoc --proto_path=IMPORT_PATH --csharp_out=DST_DIR path/to/file.proto
• IMPORT_PATH
指定.proto
解析import
指令时在其中查找文件的目录。如果省略,则使用当前目录。可以通过 --proto_path
多次传递选项来指定多个导入目录;他们将按顺序搜索。可以用作简短的形式:-I=IMPORT_PATH --proto_path
• 为了方便起见,如果DST_DIR
结束于.zip
或.jar
,编译器会将输出写入具有给定名称的单个ZIP
格式存档文件。.jar
输出还将根据Java JAR
规范的要求提供清单文件。请注意,如果输出存档已存在,则会被覆盖;编译器不够智能,无法将文件添加到现有存档中。
• 您必须提供一个或多个.proto
文件作为输入。.proto
可以一次指定多个文件。虽然文件是相对于当前目录命名的,但每个文件必须位于其中一个文件中,IMPORT_PATH
以便编译器可以确定其规范名称。
17、解析和序列化
使用协议缓冲区的全部目的是序列化您的数据,以便可以在其他地方解析它。每个生成的类都有一个WriteTo(CodedOutputStream)
方法,其中CodedOutputStream
是协议缓冲区运行时库中的一个类。但是,通常您将使用其中一种扩展方法写入常规System.IO.Stream
或将消息转换为字节数组或ByteString
。这些扩展消息在Google.Protobuf.MessageExtensions
类中,因此当您想要序列化时,通常需要命名空间的using
指令Google.Protobuf
。例如:
using Google.Protobuf;
...
Person john = ...; // Code as before
using (var output = File.Create("john.dat"))
{
john.WriteTo(output);
}
解析也很简单。每个生成的类都有一个静态Parser
属性,该属性返回该类型的MessageParser<T>
对象。这反过来又有解析流,字节数组和ByteString
的方法。因此,要解析我们刚刚创建的文件,我们可以使用:
Person john;
using (var input = File.OpenRead("john.dat"))
{
john = Person.Parser.ParseFrom(input);
}
🔚